Introduction to Explicit Neutral Models
This notebook illustrates how you can use PMD to optimize network models with an explicit neutral (EN) conductor representation. It will go through the full workflow, consisting of
importing OpenDSS network data (and applying transformations as needed);
adding OPF-specific data to the model;
optimizing the model;
inspecting the results.
This notebook will make use of the following packages in various places
"/home/runner/.julia/packages/PowerModelsDistribution/Pxoxg/src/.."
Building a case
Importing network data
OpenDSS cases with explicit neutral conductors tend to use components in a way which is not supported directly by PMD. These cases model the grounding as a 'reactor', connected between different terminals of the same bus (i.e. from the 4th terminal to ground).
In PMD, a reactor is mapped by default to a line, which will then be connected on both ends to the same bus, which is not allowed. This problem is solved by applying the data model transformation transform_loops!
, which will map the reactor to a shunt instead, or merge terminals if the reactor is in fact a short-circuit.
"conductor_ids"
1
2
3
4
5
"bus"
"b2"
"rg"
"grounded"
"status"
ENABLED::Status = 1
"terminals"
"xg"
"b1"
"rg"
"grounded"
"status"
ENABLED::Status = 1
"terminals"
"xg"
"name"
"test"
"settings"
"sbase_default"
200.0
"vbases_default"
"b1"
0.23094
"voltage_scale_factor"
1000.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"files"
"/home/runner/.julia/packages/PowerModelsDistribution/Pxoxg/src/../test/data/en_validation_case_data/test_grounding.dss"
"voltage_source"
"source"
"source_id"
"vsource.source"
"rs"
4×4 Matrix{Float64}: 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10
"va"
"status"
ENABLED::Status = 1
"connections"
"vm"
"line"
"line1"
"length"
1000.0
"t_connections"
"f_bus"
"b1"
"source_id"
"line.line1"
"status"
ENABLED::Status = 1
"t_bus"
"b2"
"data_model"
ENGINEERING::DataModel = 0
"shunt"
"line_loop.grounding"
"source_id"
"reactor.grounding"
"status"
ENABLED::Status = 1
"connections"
"dispatchable"
NO::Dispatchable = 0
"gs"
2×2 Matrix{Float64}: 1.0 -1.0 -1.0 1.0
"bus"
"b1"
"load"
"load1"
"source_id"
"load.load1"
"qd_nom"
"status"
ENABLED::Status = 1
"model"
POWER::LoadModel = 0
"connections"
"vm_nom"
0.23
"linecode"
"zabcn"
"b_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"rs"
4×4 Matrix{Float64}: 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218
"cm_ub"
"xs"
4×4 Matrix{Float64}: 0.000879326 0.00054107 0.000475546 0.000449141 0.00054107 0.000879326 0.000516533 0.000475546 0.000475546 0.000516533 0.000879326 0.00054107 0.000449141 0.000475546 0.00054107 0.000879326
"b_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Circuit has been reset with the 'clear' on line 3 in 'test_grounding.dss'
Command 'calcvoltagebases' on line 24 in 'test_grounding.dss' is not supported, skipping.
Command 'solve' on line 26 in 'test_grounding.dss' is not supported, skipping.
reactors as constant impedance elements is not yet supported, treating reactor.grounding like line
Note that this test case is very unbalanced. To obtain a more realistic scenario, we reduce the loading by a factor 3.
Adding OPF specific data
First of all, it is a good practice to remove any bounds that may have been imported from the OpenDSS network data. These can be default bounds, which might not make sense for the specific case and can lead to infeasibility. For example, this occurs for IEEE13, where the default OpenDSS line ratings are too tight for the base case power flows.
Voltage bounds
Voltage bounds are naturally expressed between two terminals.
For example, if a wye-connected load is connected between phase a
and the neutral n
, we want to apply the bounds lb <= |Ua-Un| <= ub
, in order to ensure the voltage across the load does not get too low or high. If the neutral is not modeled and assumed to be grounded everywhere, i.e. Un=0
, then this can be done equivalently by bounding Ua
directtly.
PMD comes with several data model transformations which allow you to quickly apply voltage bounds. The bounds are specified in per unit, relative to the local voltage base (phase-to-ground). If this was not the case, it would be difficult to specify bounds for multiple voltage zones at once.
First consider add_bus_absolute_vbounds!
, which allows you to specify absolute bounds, i.e. for each terminal individually. This will set the bus properties vm_lb
and vm_ub
appropriately.
"conductor_ids"
1
2
3
4
5
"bus"
"b2"
"rg"
"grounded"
"status"
ENABLED::Status = 1
"terminals"
"vm_ub"
"vm_lb"
"b1"
"rg"
"grounded"
"status"
ENABLED::Status = 1
"terminals"
"vm_ub"
"vm_lb"
"name"
"test"
"settings"
"sbase_default"
200.0
"vbases_default"
"b1"
0.23094
"voltage_scale_factor"
1000.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"files"
"/home/runner/.julia/packages/PowerModelsDistribution/Pxoxg/src/../test/data/en_validation_case_data/test_grounding.dss"
"voltage_source"
"source"
"source_id"
"vsource.source"
"rs"
4×4 Matrix{Float64}: 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10
"va"
"status"
ENABLED::Status = 1
"connections"
"vm"
"line"
"line1"
"length"
1000.0
"t_connections"
"f_bus"
"b1"
"source_id"
"line.line1"
"status"
ENABLED::Status = 1
"t_bus"
"b2"
"data_model"
ENGINEERING::DataModel = 0
"shunt"
"line_loop.grounding"
"source_id"
"reactor.grounding"
"status"
ENABLED::Status = 1
"connections"
"dispatchable"
NO::Dispatchable = 0
"gs"
2×2 Matrix{Float64}: 1.0 -1.0 -1.0 1.0
"bus"
"b1"
"load"
"load1"
"source_id"
"load.load1"
"qd_nom"
"status"
ENABLED::Status = 1
"model"
POWER::LoadModel = 0
"connections"
"vm_nom"
0.23
"linecode"
"zabcn"
"b_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"rs"
4×4 Matrix{Float64}: 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218
"xs"
4×4 Matrix{Float64}: 0.000879326 0.00054107 0.000475546 0.000449141 0.00054107 0.000879326 0.000516533 0.000475546 0.000475546 0.000516533 0.000879326 0.00054107 0.000449141 0.000475546 0.00054107 0.000879326
"b_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
The ENGINEERING
data model includes special properties to easily apply symmetrical bounds for three-phase buses (refer to the documentation for more details). These properties can be populated easily with add_bus_pn_pp_ng_vbounds!
, which specifies
phase-to-neutral (pn)
phase-to-phase (pp)
neutral-to-ground (ng)
bounds. Note that since the voltage base is in phase-to-ground, the pp
bounds have to multiplied by sqrt(3)
for a three-phase network.
"conductor_ids"
1
2
3
4
5
"bus"
"b2"
"grounded"
"vm_pp_ub"
0.44
"status"
ENABLED::Status = 1
"terminals"
"vm_pn_lb"
0.207846
"xg"
"b1"
"grounded"
"vm_pp_ub"
0.44
"status"
ENABLED::Status = 1
"terminals"
"vm_pn_lb"
0.207846
"xg"
"name"
"test"
"settings"
"sbase_default"
200.0
"vbases_default"
"b1"
0.23094
"voltage_scale_factor"
1000.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"files"
"/home/runner/.julia/packages/PowerModelsDistribution/Pxoxg/src/../test/data/en_validation_case_data/test_grounding.dss"
"voltage_source"
"source"
"source_id"
"vsource.source"
"rs"
4×4 Matrix{Float64}: 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10
"va"
"status"
ENABLED::Status = 1
"connections"
"vm"
"line"
"line1"
"length"
1000.0
"t_connections"
"f_bus"
"b1"
"source_id"
"line.line1"
"status"
ENABLED::Status = 1
"t_bus"
"b2"
"data_model"
ENGINEERING::DataModel = 0
"shunt"
"line_loop.grounding"
"source_id"
"reactor.grounding"
"status"
ENABLED::Status = 1
"connections"
"dispatchable"
NO::Dispatchable = 0
"gs"
2×2 Matrix{Float64}: 1.0 -1.0 -1.0 1.0
"bus"
"b1"
"load"
"load1"
"source_id"
"load.load1"
"qd_nom"
"status"
ENABLED::Status = 1
"model"
POWER::LoadModel = 0
"connections"
"vm_nom"
0.23
"linecode"
"zabcn"
"b_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"rs"
4×4 Matrix{Float64}: 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218
"xs"
4×4 Matrix{Float64}: 0.000879326 0.00054107 0.000475546 0.000449141 0.00054107 0.000879326 0.000516533 0.000475546 0.000475546 0.000516533 0.000879326 0.00054107 0.000449141 0.000475546 0.00054107 0.000879326
"b_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Often, the true goal of voltage bounds is to protect the connected 'units', i.e. loads, generators etc. Therefore, it can suffice to apply voltage bounds only to those buses and terminals which actually have a unit connected to them. This is what add_unit_vbounds!
is for. The delta_multiplier
is the correction factor which is applied to the specified bounds if a unit has configuration=DELTA
.
"conductor_ids"
1
2
3
4
5
"bus"
"b2"
"vm_pair_lb"
"grounded"
"vm_pp_ub"
0.44
"vm_pair_ub"
"status"
ENABLED::Status = 1
"terminals"
"b1"
"grounded"
"vm_pp_ub"
0.44
"status"
ENABLED::Status = 1
"terminals"
"vm_pn_lb"
0.207846
"xg"
"name"
"test"
"settings"
"sbase_default"
200.0
"vbases_default"
"b1"
0.23094
"voltage_scale_factor"
1000.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"files"
"/home/runner/.julia/packages/PowerModelsDistribution/Pxoxg/src/../test/data/en_validation_case_data/test_grounding.dss"
"voltage_source"
"source"
"source_id"
"vsource.source"
"rs"
4×4 Matrix{Float64}: 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10 3.96342e-11 3.96342e-11 3.96342e-11 3.96342e-11 4.27691e-10
"va"
"status"
ENABLED::Status = 1
"connections"
"vm"
"line"
"line1"
"length"
1000.0
"t_connections"
"f_bus"
"b1"
"source_id"
"line.line1"
"status"
ENABLED::Status = 1
"t_bus"
"b2"
"data_model"
ENGINEERING::DataModel = 0
"shunt"
"line_loop.grounding"
"source_id"
"reactor.grounding"
"status"
ENABLED::Status = 1
"connections"
"dispatchable"
NO::Dispatchable = 0
"gs"
2×2 Matrix{Float64}: 1.0 -1.0 -1.0 1.0
"bus"
"b1"
"load"
"load1"
"source_id"
"load.load1"
"qd_nom"
"status"
ENABLED::Status = 1
"model"
POWER::LoadModel = 0
"connections"
"vm_nom"
0.23
"linecode"
"zabcn"
"b_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"rs"
4×4 Matrix{Float64}: 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218 5.92176e-5 5.92176e-5 5.92176e-5 5.92176e-5 0.000227218
"xs"
4×4 Matrix{Float64}: 0.000879326 0.00054107 0.000475546 0.000449141 0.00054107 0.000879326 0.000516533 0.000475546 0.000475546 0.000516533 0.000879326 0.00054107 0.000449141 0.000475546 0.00054107 0.000879326
"b_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_to"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"g_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Whether it is desirable to apply as many bounds as possible, or only ones which are really needed, will depend on the chosen formulation later on. In any case, these data model transformations make it easy for the user to apply any desired bounds.
You might have noticed that applying all these transformations will lead to redundant constraints. This is resolved in the data model transformation, which determines a set of absolute and pairwise voltage constraints which imply all other ones.
For example, let's inspect bus b2
. This bus has a single-phase load between terminals 1 and 4, so only for that pair the lower bound should be 0.91 instead of 0.9. For an explanation of how the vm_pair_lb
property is structured, refer to the documentation.
1
4
0.91
2
4
0.9
3
4
0.9
1
2
1.55885
1
3
1.55885
2
3
1.55885
Adding a generator
So far, we imported a power flow case from OpenDSS. This means that the problem is fully determined, i.e. there is no degrees of freedom left over which to optimize. Therefore, we will add an additional generator to the problem.
"qg_ub"
0.0
"cost_pg_parameters"
0.0
0.0
0.0
"status"
ENABLED::Status = 1
"qg_lb"
0.0
"connections"
2
4
"pg_ub"
20.0
"pg_lb"
0.0
"bus"
"b2"
"configuration"
WYE::ConnConfig = 0
Solving a simple OPF problem
First of all, we apply the data model transformation to go from ENGINEERING
to MATHEMATICAL
. We need to pass the flags kron_reduce=false
and phase_project=false
; these activate data model modifications which are required for some formulations, but not for the EN ones.
"is_kron_reduced"
false
"conductor_ids"
1
2
3
4
5
"time_elapsed"
1.0
"bus"
"1"
"vm_pair_lb"
"grounded"
"vm_pp_ub"
1.90526
"vm_pair_ub"
"bus_i"
1
"name"
"b2"
"2"
"vm_pair_lb"
"grounded"
"vm_pp_ub"
1.90526
"vm_pair_ub"
"bus_i"
2
"name"
"b1"
"3"
"vm_pair_lb"
"source_id"
"voltage_source.source"
"grounded"
"vmin"
"vm_pair_ub"
"bus_i"
3
"name"
"test"
"map"
⌀ (This table has no columns) | |||
1 | "unmap_function" "_map_math2eng_root!" | ||
---|---|---|---|
2 | "to" "bus.1" | "from" "b2" | "unmap_function" "_map_math2eng_bus!" |
3 | "to" "bus.2" | "from" "b1" | "unmap_function" "_map_math2eng_bus!" |
4 | "to" "branch.1" | "from" "line1" | "unmap_function" "_map_math2eng_line!" |
5 | "to" "load.1" | "from" "load1" | "unmap_function" "_map_math2eng_load!" |
6 | "to" "shunt.1" | "from" "line_loop.grounding" | "unmap_function" "_map_math2eng_shunt!" |
7 | "to" "gen.1" | "from" "g1" | "unmap_function" "_map_math2eng_generator!" |
8 | "to" "gen.2" "bus.3" "branch.2" | "from" "source" | "unmap_function" "_map_math2eng_voltage_source!" |
"settings"
"sbase_default"
200.0
"vbases_default"
"2"
0.23094
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"gen"
"1"
"pg"
"model"
2
"connections"
"shutdown"
0.0
"startup"
0.0
"configuration"
WYE::ConnConfig = 0
"2"
"pg"
"model"
2
"connections"
"shutdown"
0.0
"startup"
0.0
"configuration"
WYE::ConnConfig = 0
"branch"
"1"
"br_r"
4×4 Matrix{Float64}: 0.852066 0.222066 0.222066 0.222066 0.222066 0.852066 0.222066 0.222066 0.222066 0.222066 0.852066 0.222066 0.222066 0.222066 0.222066 0.852066
"rate_a"
"f_connections"
"rate_b"
"name"
"line1"
"br_x"
4×4 Matrix{Float64}: 3.29747 2.02901 1.7833 1.68428 2.02901 3.29747 1.937 1.7833 1.7833 1.937 3.29747 2.02901 1.68428 1.7833 2.02901 3.29747
"2"
"source_id"
"voltage_source.source"
"t_connections"
"f_bus"
3
"br_r"
4×4 Matrix{Float64}: 1.60384e-9 1.48628e-10 1.48628e-10 1.48628e-10 1.48628e-10 1.60384e-9 1.48628e-10 1.48628e-10 1.48628e-10 1.48628e-10 1.60384e-9 1.48628e-10 1.48628e-10 1.48628e-10 1.48628e-10 1.60384e-9
"b_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"br_status"
1
"storage"
"switch"
"is_projected"
false
"per_unit"
true
"data_model"
MATHEMATICAL::DataModel = 1
"shunt"
"1"
"source_id"
"shunt.line_loop.grounding"
"name"
"line_loop.grounding"
"shunt_bus"
2
"status"
1
"connections"
"dispatchable"
0
"transformer"
"bus_lookup"
"b2"
1
"b1"
2
"load"
"1"
"model"
POWER::LoadModel = 0
"connections"
"configuration"
WYE::ConnConfig = 0
"name"
"load1"
"status"
1
"qd"
updated generator 1 cost function with order 3 to a function of order 2: [0.0, 0.0]
Before solving the problem, it is important to add initialization values for the voltage variables. Failing to do so will almost always result in solver issues.
For a single-phase equivalent network, this is very easy to do; simply initialize each voltage variable to 1.0+im*0.0
, which usually corresponds to the voltage profile without any network load and ignoring all shunts and linecharging (refered to as 'no-load voltage').
The equivalent in a general, multi-conductor network is less trivial; for example, transformers can shift the no-load voltage angle of individual terminals in complicated ways. Therefore, we developed the method add_start_vrvi!
, which will infer the no-load voltage for each terminal in the network and add initialization properties to the MATHEMATICAL
data model.
"is_kron_reduced"
false
"conductor_ids"
1
2
3
4
5
"time_elapsed"
1.0
"bus"
"1"
"vm_pair_lb"
"grounded"
"vm_pp_ub"
1.90526
"vm_pair_ub"
"bus_i"
1
"vr_start"
"2"
"vm_pair_lb"
"grounded"
"vm_pp_ub"
1.90526
"vm_pair_ub"
"bus_i"
2
"vr_start"
"3"
"vm_pair_lb"
"source_id"
"voltage_source.source"
"grounded"
"vmin"
"vm_pair_ub"
"bus_i"
3
"name"
"test"
"map"
⌀ (This table has no columns) | |||
1 | "unmap_function" "_map_math2eng_root!" | ||
---|---|---|---|
2 | "to" "bus.1" | "from" "b2" | "unmap_function" "_map_math2eng_bus!" |
3 | "to" "bus.2" | "from" "b1" | "unmap_function" "_map_math2eng_bus!" |
4 | "to" "branch.1" | "from" "line1" | "unmap_function" "_map_math2eng_line!" |
5 | "to" "load.1" | "from" "load1" | "unmap_function" "_map_math2eng_load!" |
6 | "to" "shunt.1" | "from" "line_loop.grounding" | "unmap_function" "_map_math2eng_shunt!" |
7 | "to" "gen.1" | "from" "g1" | "unmap_function" "_map_math2eng_generator!" |
8 | "to" "gen.2" "bus.3" "branch.2" | "from" "source" | "unmap_function" "_map_math2eng_voltage_source!" |
"settings"
"sbase_default"
200.0
"vbases_default"
"2"
0.23094
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"gen"
"1"
"pg"
"model"
2
"connections"
"shutdown"
0.0
"startup"
0.0
"configuration"
WYE::ConnConfig = 0
"2"
"pg"
"model"
2
"connections"
"shutdown"
0.0
"startup"
0.0
"configuration"
WYE::ConnConfig = 0
"branch"
"1"
"br_r"
4×4 Matrix{Float64}: 0.852066 0.222066 0.222066 0.222066 0.222066 0.852066 0.222066 0.222066 0.222066 0.222066 0.852066 0.222066 0.222066 0.222066 0.222066 0.852066
"rate_a"
"f_connections"
"rate_b"
"name"
"line1"
"br_x"
4×4 Matrix{Float64}: 3.29747 2.02901 1.7833 1.68428 2.02901 3.29747 1.937 1.7833 1.7833 1.937 3.29747 2.02901 1.68428 1.7833 2.02901 3.29747
"2"
"source_id"
"voltage_source.source"
"t_connections"
"f_bus"
3
"br_r"
4×4 Matrix{Float64}: 1.60384e-9 1.48628e-10 1.48628e-10 1.48628e-10 1.48628e-10 1.60384e-9 1.48628e-10 1.48628e-10 1.48628e-10 1.48628e-10 1.60384e-9 1.48628e-10 1.48628e-10 1.48628e-10 1.48628e-10 1.60384e-9
"b_fr"
4×4 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"br_status"
1
"storage"
"switch"
"is_projected"
false
"per_unit"
true
"data_model"
MATHEMATICAL::DataModel = 1
"shunt"
"1"
"source_id"
"shunt.line_loop.grounding"
"name"
"line_loop.grounding"
"shunt_bus"
2
"status"
1
"connections"
"dispatchable"
0
"transformer"
"bus_lookup"
"b2"
1
"b1"
2
"load"
"1"
"model"
POWER::LoadModel = 0
"connections"
"configuration"
WYE::ConnConfig = 0
"name"
"load1"
"status"
1
"qd"
Now we are ready to solve the OPF problem.
"solve_time"
1.14636
"optimizer"
"Ipopt"
"termination_status"
LOCALLY_SOLVED::TerminationStatusCode = 4
"dual_status"
FEASIBLE_POINT::ResultStatusCode = 1
"primal_status"
FEASIBLE_POINT::ResultStatusCode = 1
"objective"
-0.00322506
"solution"
"branch"
"1"
"2"
"gen"
"1"
"2"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"bus"
"1"
"2"
"3"
"per_unit"
true
"objective_lb"
-Inf
Inspecting results
The result dictionary contains the solutions.
"branch"
"1"
"cr_fr"
"qf"
"ci_fr"
"csi_fr"
"qt"
"csr_fr"
"2"
"cr_fr"
"qf"
"ci_fr"
"csi_fr"
"qt"
"csr_fr"
"gen"
"1"
"crg"
"qg"
"pg"
"cig"
"2"
"crg"
"qg"
"pg"
"cig"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"2"
0.23094
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"qd_bus"
"cid"
"crd_bus"
"crd"
"pd_bus"
"qd"
"bus"
"1"
"vi"
"vr"
"2"
"vi"
"vr"
"3"
"vi"
"vr"
"per_unit"
true
Next, we can transform the solution back to the ENGINEERING
data model.
"voltage_source"
"source"
"crg"
"qg"
"pg"
"cig"
"line"
"line1"
"cr_fr"
"qf"
"ci_fr"
"csi_fr"
"qt"
"csr_fr"
"settings"
"sbase"
200.0
"generator"
"g1"
"crg"
"qg"
"pg"
"cig"
"load"
"load1"
"qd_bus"
"cid"
"crd_bus"
"crd"
"pd_bus"
"qd"
"bus"
"b2"
"vi"
"vr"
"b1"
"vi"
"vr"
"per_unit"
false
For example, let's inspect the dispatched active generator power.
5.07975
The active generator power bounds are not active, 0 <= 5.15 <= 10.0
. This means some other bound is active, because since g1
has zero cost, it is normally more optimal to dispatch more active power, which the source bus generator will pay the default price for.
So, let's inspect the voltage at bus b2
. We will obtain the voltage base through 'data_math', which will allow us to inspect the voltage in pu. This will make it easier to compare the values against the bounds we specified before.
0.995907
1.01406
1.00269
0.1
As it turns out, the voltage magnitude constraint om the neutral terminal is binding, vm_b2_pu[4]=0.1
.
Available formulations
We create a results
dictionary which will collect the result dictionary generated by each formulation. We will end this section with a comparison of them.
There are several formulations available which support explicit neutrals. The main distinction is whether the flow variables are current or power variables.
Current flow variables
The preferred class of exact formulations are IVR
, i.e. with current flow variables (I) and rectangular voltage variables (VR).
IVRENPowerModel
is a non-linear formulation.
"solve_time"
0.0275071
"optimizer"
"Ipopt"
"termination_status"
LOCALLY_SOLVED::TerminationStatusCode = 4
"dual_status"
FEASIBLE_POINT::ResultStatusCode = 1
"primal_status"
FEASIBLE_POINT::ResultStatusCode = 1
"objective"
-0.00322506
"solution"
"branch"
"1"
"2"
"gen"
"1"
"2"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"bus"
"1"
"2"
"3"
"per_unit"
true
"objective_lb"
-Inf
IVRQuadraticENPowerModel
is an equivalent quadratic formulation.
"solve_time"
0.0151401
"optimizer"
"Ipopt"
"termination_status"
LOCALLY_SOLVED::TerminationStatusCode = 4
"dual_status"
FEASIBLE_POINT::ResultStatusCode = 1
"primal_status"
FEASIBLE_POINT::ResultStatusCode = 1
"objective"
-0.00322506
"solution"
"branch"
"1"
"2"
"gen"
"1"
"2"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"bus"
"1"
"2"
"3"
"per_unit"
true
"objective_lb"
-Inf
Reduced
models only create explicit series current variables, and create the total current variables as linear expressions of those. Since branches tend to be the dominate component in number, this can lead to a big reduction in the number of variables.
IVRReducedENPowerModel
is the branch-reduced version of IVRENPowerModel
.
"solve_time"
0.0233951
"optimizer"
"Ipopt"
"termination_status"
LOCALLY_SOLVED::TerminationStatusCode = 4
"dual_status"
FEASIBLE_POINT::ResultStatusCode = 1
"primal_status"
FEASIBLE_POINT::ResultStatusCode = 1
"objective"
-0.00322506
"solution"
"branch"
"1"
"2"
"gen"
"1"
"2"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"bus"
"1"
"2"
"3"
"per_unit"
true
"objective_lb"
-Inf
IVRReducedQuadraticENPowerModel
is the branch-reduced version of IVRQuadraticENPowerModel
.
"solve_time"
0.012799
"optimizer"
"Ipopt"
"termination_status"
LOCALLY_SOLVED::TerminationStatusCode = 4
"dual_status"
FEASIBLE_POINT::ResultStatusCode = 1
"primal_status"
FEASIBLE_POINT::ResultStatusCode = 1
"objective"
-0.00322506
"solution"
"branch"
"1"
"2"
"gen"
"1"
"2"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"bus"
"1"
"2"
"3"
"per_unit"
true
"objective_lb"
-Inf
Power flow variables
We also included a single formulation with power flow variables. Because it also has rectangular voltage variables and legacy reasons, it is referred to as ACRENPowerModel
.
"solve_time"
1.01999
"optimizer"
"Ipopt"
"termination_status"
LOCALLY_SOLVED::TerminationStatusCode = 4
"dual_status"
FEASIBLE_POINT::ResultStatusCode = 1
"primal_status"
FEASIBLE_POINT::ResultStatusCode = 1
"objective"
-0.0169915
"solution"
"branch"
"1"
"2"
"gen"
"1"
"2"
"multiinfrastructure"
false
"settings"
"sbase_default"
200.0
"vbases_default"
"voltage_scale_factor"
1000.0
"sbase"
200.0
"power_scale_factor"
1000.0
"base_frequency"
60.0
"multinetwork"
false
"load"
"1"
"bus"
"1"
"2"
"3"
"per_unit"
true
"objective_lb"
-Inf
We mentioned before that the IVR
formulations are preferred for EN
models. This is because in EN
models, the voltage magnitude cannot be bounded below for some terminals (the ones belonging to the neutral conductor). In a formulation with power flow variables, this means that KCL cannot be enforced for those terminals.
In short, ACR allows non-physical groundings of the neutral conductor, and is therefore a relaxation of the original problem. To demonstrate this, let's create a summary of the obtained objective values and generator set points.
formulation | objective value | g1 pg | |
---|---|---|---|
String | Float64 | Float64 | |
1 | "ACRENPowerModel" | -0.0169915 | 20.0 |
2 | "IVRENPowerModel" | -0.00322506 | 5.07975 |
3 | "IVRQuadraticENPowerModel" | -0.00322506 | 5.07975 |
4 | "IVRReducedENPowerModel" | -0.00322506 | 5.07975 |
5 | "IVRReducedQuadraticENPowerModel" | -0.00322506 | 5.07975 |
This table illustrates that ACRENPowerModel
is a relaxation of the other ones. However, note that it is not guaranteed that the objective value will be lower, because all problems are only solved to local optimality. And in fact, the ACR
solution is very sensitive to changes in the initialization. The optional virtual groundings seem to introduce many potential local optima.